/**
 * uni-pay 统一支付
 * 请勿修改此处代码，因为插件更新后此处代码会被覆盖。
 * 作者：VK
 */
'use strict';
const crypto = require("crypto");

const uniPay = require("uni-pay");

const configCenter = require("uni-config-center");

const config = configCenter({ pluginId: 'uni-pay' }).requireFile('config.js');

const payDao = require('./libs/dao/payDao');

const fnUtil = require('./libs/fnUtil');

const WxPayV3 = require("./libs/wxpay-v3");

var vkPay = {};
vkPay.pubfn = fnUtil;
vkPay.request = fnUtil.request;

vkPay.crypto = require('./libs/crypto');

const notifyPath = "/vk-pay-notify/";

const db = uniCloud.database();
const _ = db.command;
/**
 * 获取支付插件的完整配置
 */
vkPay.getConfig = () => {
	return config;
}
/**
 * 支付成功 - 异步通知
 */
vkPay.paymentNotify = async (obj = {}) => {
	let { event, context, orderPaySuccess } = obj;
	const {
		alipayAppPayToH5Pay
	} = config;
	let { out_trade_no } = fnUtil.getNotifyData(event, notifyPath);
	let payOrderInfo = await payDao.findPayOrdersByOutTradeNo(out_trade_no);
	let pay_type = event.path.substring(notifyPath.length) || payOrderInfo.pay_type;
	if (payOrderInfo.pay_type !== pay_type) payOrderInfo.pay_type = pay_type;
	// 此处是为了让app支付直接调用H5支付,这样不用去申请APP申请接口权限
	if (alipayAppPayToH5Pay && pay_type == "alipay_app-plus") pay_type = "alipay_h5";
	let uniPayInstance = await vkPay.initUniPayInstance(payOrderInfo);
	// uni-pay 1.0.17 新增了判断通知类型接口 checkNotifyType
	if (typeof uniPayInstance.checkNotifyType === "function") {
		let notifyType = await uniPayInstance.checkNotifyType(event);
		console.log("notifyType：", notifyType);
		if (notifyType !== "payment") {
			// 由于支付宝部分退款会触发支付成功的回调，但同时签名验证是算未通过的，为了避免支付宝重复推送，这里可以直接返回成功告知支付宝服务器，不用再推送过来了。
			return vkPay.returnNotifySUCCESS(pay_type);
		}
	}
	let verifyResult = await uniPayInstance.verifyPaymentNotify(event);
	if (!verifyResult) {
		console.log('---------!签名验证未通过!---------');
		console.log('---------!签名验证未通过!---------');
		console.log('---------!签名验证未通过!---------');
		return {}
	}
	console.log('---------!签名验证通过!---------');
	verifyResult = JSON.parse(JSON.stringify(verifyResult)); // 这一句代码有用，请勿删除。
	let {
		outTradeNo,
		totalFee,
		transactionId,
		resultCode,
		openid,
		appId,
		authAppId,
		sellerId
	} = verifyResult;
	let wxpay_info;
	let alipay_info;
	let passback_params;
	if (pay_type.indexOf("wxpay_") == 0) {
		// 微信支付
		wxpay_info = verifyResult;
		if (verifyResult.attach) {
			passback_params = decodeURIComponent(verifyResult.attach);
			try {
				passback_params = JSON.parse(passback_params);
			} catch (err) {}
		}
	} else if (pay_type.indexOf("alipay_") == 0) {
		// 支付宝支付
		alipay_info = verifyResult;
		if (verifyResult.passbackParams) {
			passback_params = decodeURIComponent(verifyResult.passbackParams);
			try {
				passback_params = JSON.parse(passback_params);
			} catch (err) {}
		}
	}
	// 支付宝部分退款成功后，也会触发此回调，故需要判断此次回调是支付成功回调还是部分退款回调
	let isPayNotify = true;
	if (alipay_info && (alipay_info.outBizNo || alipay_info.refundFee)) {
		isPayNotify = false;
		console.log('---------!不是付款成功通知，已为您忽略!---------');
	}
	if (resultCode == "SUCCESS" && isPayNotify) {
		let notify_time = new Date().getTime();
		let updateNum = await payDao.updatePayOrdersByWhereJson({
			whereJson: {
				status: 0, // status:0 为必须条件，防止重复推送时的错误
				out_trade_no: outTradeNo, // 商户订单号
			},
			dataJson: {
				status: 1, // 设置为已付款
				transaction_id: transactionId, // 第三方支付单号
				notify_time: notify_time, // 通知时间
				auth_appid: authAppId,
				seller_id: sellerId,
				pay_type, // 回调类型
				notify_num: _.inc(1), // 通知次数，这里只会是0或1
			}
		});
		if (updateNum > 0) {
			// 只有首次推送才执行用户自己的逻辑处理。
			// 用户自己的逻辑处理 开始-----------------------------------------------------------
			// let payOrderInfo = await payDao.findPayOrdersByOutTradeNo(outTradeNo); // 由于已经在上方获取过payOrderInfo，故此句代码可删除。
			let userOrderSuccess = false;
			if (typeof orderPaySuccess === "function") {
				console.log('用户自己的回调逻辑 - 开始执行');
				userOrderSuccess = await orderPaySuccess({
					verifyResult,
					data: {
						...payOrderInfo,
						status: 1, // 设置为已付款
						transaction_id: transactionId,
						original_data: event.body,
						wxpay_info: wxpay_info,
						alipay_info: alipay_info,
						notify_time: notify_time
					},
					passback_params,
				}, { db, _, ...obj });
				console.log('用户自己的回调逻辑 - 执行完成');
			}
			console.log('userOrderSuccess', userOrderSuccess);
			// 用户自己的逻辑处理 结束-----------------------------------------------------------

			// 存本次的回调数据（为了留个备份，方便核查数据）
			try {
				await payDao.updatePayOrdersByWhereJson({
					whereJson: {
						status: 1,
						out_trade_no: outTradeNo,
					},
					dataJson: {
						original_data: event.body,
						wxpay_info: wxpay_info,
						alipay_info: alipay_info,
						user_order_success: userOrderSuccess,
					}
				});
			} catch (err) {
				console.error("存本次的回调数据异常：", err);
			}
		} else {
			console.log('---------！注意：本次回调非首次回调，已被插件拦截，插件不会执行你的回调函数！---------');
			console.log('---------！注意：本次回调非首次回调，已被插件拦截，插件不会执行你的回调函数！---------');
			console.log('---------！注意：本次回调非首次回调，已被插件拦截，插件不会执行你的回调函数！---------');
			console.log('verifyResult:', verifyResult)
		}
	}
	// 给第三方服务器回复 开始-----------------------------------------------------------
	return vkPay.returnNotifySUCCESS(pay_type);
	// 给第三方服务器回复 结束-----------------------------------------------------------
};
vkPay.returnNotifySUCCESS = function(pay_type) {
	if (pay_type.indexOf("wxpay_") == 0) {
		// 微信支付需返回 xml 格式的字符串
		return {
			mpserverlessComposedResponse: true,
			statusCode: 200,
			headers: {
				'content-type': 'text/xml;charset=utf-8'
			},
			body: "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>"
		};
	} else if (pay_type.indexOf("alipay_") == 0) {
		// 支付宝支付直接返回 success 字符串
		return {
			mpserverlessComposedResponse: true,
			statusCode: 200,
			headers: {
				'content-type': 'text/plain'
			},
			body: "success"
		}
	}
	return "success";
};


/**
 * 统一支付 - 创建支付
 * @param {String} provider    供应商:wxpay、alipay
 * @param {Object} context     请求上下文
 * @param {Object} data        订单参数
 * data参数
 * @param {String} openid  		用户openid，小程序支付时必传
 * @param {String} out_trade_no  商户支付订单号
 * @param {Number} total_fee    订单金额(单位分 100 = 1元)
 * @param {String} subject     订单标题
 * @param {String} body        订单详情
 * @param {String} type        订单类型 goods：订单付款 recharge：余额充值付款 vip：vip充值付款 等等，可自定义
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 * @param {String} out_trade_no 商户支付订单号
 * @param {Object} orderInfo  支付订单信息
 */
vkPay.createPayment = async (obj = {}) => {
	let {
		data = {},
			provider,
			context,
			alipayAppPayToH5Pay: myAlipayAppPayToH5Pay,
			isPC,
	} = obj;
	if (!context) context = uniCloud.$context;
	let {
		pid, // 商户id，若此参数有值，则会从数据库中获取支付配置进行支付
		app_auth_token, // 支付宝服务商模式下，子商户的授权token，若该参数有值，自动视为支付宝服务商模式
		openid,
		out_trade_no,
		total_fee,
		subject,
		body,
		passback_params,
		type,
		custom,
		other
	} = data;
	if (!out_trade_no || typeof out_trade_no !== "string") {
		return { code: -1, msg: "商户订单号必须是string类型，且不能为空" };
	}
	if (!type || typeof type !== "string") {
		return { code: -1, msg: 'type必须是string类型，且不能为空，如设置为goods代表商品订单' };
	}
	if (typeof total_fee !== "number" || total_fee <= 0 || total_fee % 1 !== 0) {
		return { code: -1, msg: 'total_fee必须为正整数（>0的整数）（注意：100=1元）' };
	}
	if (!subject || typeof subject !== "string") {
		return { code: -1, msg: "subject必须是string类型，且不能为空" };
	}
	if (!provider || typeof provider !== "string") {
		return { code: -1, msg: "provider必须是string类型，且不能为空" };
	}
	if (provider === "wxpay" && !body) body = subject;
	let res = { code: 0, msg: 'ok', out_trade_no };
	// 获取支付配置
	const {
		notifyUrl,
		sysServiceProviderId
	} = config;
	let { alipayAppPayToH5Pay } = config;
	if (typeof myAlipayAppPayToH5Pay !== "undefined") alipayAppPayToH5Pay = myAlipayAppPayToH5Pay;
	// 业务逻辑开始-----------------------------------------------------------
	let spaceId = context.SPACEINFO.spaceId; // 服务空间ID
	let currentNotifyUrl = notifyUrl[spaceId]; // 异步回调地址
	if (!currentNotifyUrl || currentNotifyUrl.indexOf("http") !== 0) {
		return { code: -1, msg: "请先配置正确的异步回调URL" }
	}
	let {
		CLIENTUA = "",
			PLATFORM,
			CLIENTIP
	} = context;
	PLATFORM = fnUtil.getPlatform(PLATFORM);
	if (PLATFORM === "h5" && provider === "wxpay" && fnUtil.isMobile(CLIENTUA, isPC)) {
		if (CLIENTUA.toLowerCase().indexOf("micromessenger") == -1) {
			// 微信手机外部浏览器支付
			PLATFORM = "mweb";
		} else {
			PLATFORM = "h5-weixin";
			if (!openid) {
				return { code: -1, msg: "用户openid不能为空！" }
			}
		}
	}
	let pay_type = provider + "_" + PLATFORM; // 支付方式
	// 此处是为了让app支付直接调用H5支付,这样不用去申请APP申请接口权限
	if (alipayAppPayToH5Pay && pay_type == "alipay_app-plus") pay_type = "alipay_h5";
	// 拼接实际异步回调地址(在路径上体现了支付方式)
	let finalNotifyUrl = currentNotifyUrl + notifyPath + pay_type;
	// 初始化uniPayInstance
	let tradeType = vkPay.getTradeType(pay_type);
	let uniPayConifg = await vkPay.getUniPayConfig({ pay_type, app_auth_token, pid });
	let uniPayInstance = await vkPay.initUniPayInstance({ pay_type, app_auth_token, pid });

	// 获取支付信息
	let getOrderInfoData = {
		openid: openid,
		subject: subject,
		body: body,
		outTradeNo: out_trade_no,
		totalFee: total_fee,
		notifyUrl: finalNotifyUrl,
		tradeType: tradeType
	};
	if (pay_type === "wxpay_mweb") {
		getOrderInfoData.spbillCreateIp = CLIENTIP;
		getOrderInfoData.sceneInfo = uniPayConifg.sceneInfo;
	}
	// 第三方支付服务器返回的订单信息
	let orderInfo;
	try {
		if (provider === "alipay") {
			// 支付宝支付特殊参数
			if (typeof data.extendParams === "undefined" && sysServiceProviderId) {
				getOrderInfoData.extendParams = { sysServiceProviderId };
			}
			if (uniPayConifg.sysServiceProviderId) {
				getOrderInfoData.extendParams = { sysServiceProviderId: uniPayConifg.sysServiceProviderId };
			}
			if (passback_params) {
				// 支付宝需要进行encodeURIComponent编码
				getOrderInfoData.passbackParams = encodeURIComponent(JSON.stringify(passback_params));
			}
		} else if (provider === "wxpay") {
			// 微信支付特殊参数
			if (passback_params) {
				getOrderInfoData.attach = JSON.stringify(passback_params);
			}
		}
		if (other) {
			// other 内的键名转驼峰
			other = fnUtil.snake2camelJson(other);
			getOrderInfoData = Object.assign(getOrderInfoData, other);
		}
		res.orderInfo = await uniPayInstance.getOrderInfo(getOrderInfoData);
		// 支付宝支付参数特殊处理
		if (provider === "alipay") {
			if (typeof res.orderInfo === "object" && res.orderInfo.code && res.orderInfo.code !== "10000") {
				res.code = res.orderInfo.code;
				res.msg = res.orderInfo.subMsg;
			}
		}
	} catch (e) {
		let errorMsg = e.errorMessage || e.message;
		console.error("getOrderInfoData: ", getOrderInfoData);
		console.error("data: ", data);
		console.error("err: ", e);
		console.error("errMsg: ", errorMsg);
		return { code: -3, msg: "获取支付信息失败，请稍后再试。" + errorMsg };
	}
	// 判断是否存在
	let payOrderInfo = await payDao.findPayOrdersByOutTradeNo(out_trade_no);
	if (!payOrderInfo) {
		// 添加数据库(数据库的out_trade_no字段需设置为唯一索引)
		await payDao.addPayOrders({
			status: 0,
			pid,
			app_auth_token,
			appid: uniPayConifg.appId,
			pay_type,
			platform: PLATFORM,
			client_ip: CLIENTIP,
			type,
			out_trade_no,
			openid,
			total_fee,
			passback_params,
			...custom,
			other,
		});
	} else {
		// 如果订单已经存在，则只修改订单支付方式（用户可能先点微信支付，未付款，又点了支付宝支付）
		await payDao.updatePayOrdersById(payOrderInfo._id, {
			pay_type,
			openid
		});
	}
	// 自动删除1天前的订单（未付款订单）
	await payDao.deleteExpPayOrders();
	// 业务逻辑结束-----------------------------------------------------------
	return res;
};


/**
 * 统一支付结果查询
 * @description 根据商户订单号或者平台订单号查询订单信息，主要用于未接收到支付通知时可以使用此接口进行支付结果验证
 * data 请求参数 说明
 * @param {String} out_trade_no 商户订单号
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.queryPayment = async (obj = {}) => {
	let {
		out_trade_no, // 商户订单号
		transaction_id, // 支付平台的订单号
		await_notify = false, // 是否需要等待异步通知执行完成才返回前端支付结果
		pay_order_info = false, // 是否需要返回订单信息
	} = obj;
	let res = { code: 0, msg: 'ok' };
	// 业务逻辑开始-----------------------------------------------------------
	if (!out_trade_no) {
		return { code: -1, msg: '订单号不能为空' }
	}
	let payOrder = await payDao.findPayOrdersByOutTradeNo(out_trade_no);
	if (!payOrder) {
		return { code: -2, msg: '订单不存在或订单未支付!' }
	}
	// 初始化uniPayInstance
	let uniPayInstance = await vkPay.initUniPayInstance(payOrder);
	let orderQueryJson = {};
	if (out_trade_no) {
		orderQueryJson.outTradeNo = out_trade_no;
	} else {
		orderQueryJson.transactionId = transaction_id;
	}
	let queryResult = await uniPayInstance.orderQuery(orderQueryJson);
	if (queryResult.tradeState === 'SUCCESS' || queryResult.tradeState === 'FINISHED') {
		if (typeof payOrder.user_order_success == "undefined" && await_notify) {
			let whileTime = 0; // 当前循环已执行的时间（毫秒）
			let whileInterval = 500; // 每次循环间隔时间（毫秒）
			let maxTime = 5000; // 循环执行时间超过此值则退出循环（毫秒）
			while (typeof payOrder.user_order_success == "undefined" && whileTime <= maxTime) {
				await fnUtil.sleep(whileInterval);
				whileTime += whileInterval;
				payOrder = await payDao.findPayOrdersByOutTradeNo(out_trade_no);
			}
		}
		res = {
			code: 0,
			msg: "支付成功",
			orderPaid: true, // 标记用户是否已付款成功（此参数只能表示用户确实付款了，但系统的异步回调逻辑可能还未执行完成）
			out_trade_no, // 商家订单号
			transaction_id, // 支付平台订单号
			status: payOrder.status, // 标记当前支付订单状态 0：未支付 1：已支付 2：已部分退款 3：已全额退款
			notify: payOrder.user_order_success, // 用户异步通知逻辑是否全部执行完成，且无异常（建议前端通过此参数是否为true来判断是否支付成功）
		}
		if (pay_order_info) {
			res.payOrder = payOrder;
		}
	} else {
		let errMsg = queryResult.tradeStateDesc || "未支付或已退款";
		if (errMsg.indexOf("订单发生过退款") > -1) {
			errMsg = "订单已退款";
		}
		res = {
			code: -1,
			msg: errMsg,
			orderPaid: false,
			out_trade_no, // 商家订单号
			transaction_id, // 支付平台订单号
		}
	}
	// 业务逻辑结束-----------------------------------------------------------
	return res;
};
/**
 * 查询退款结果
 * @description 提交退款申请后，通过调用该接口查询退款状态。
 * data 请求参数 说明
 * @param {String} out_trade_no 商户订单号
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.queryRefund = async (obj = {}) => {
	let {
		out_trade_no,
	} = obj;
	let res = { code: 0, msg: 'ok' };
	if (!out_trade_no) {
		return { code: -1, msg: '订单号不能为空' }
	}
	let payOrder = await payDao.findPayOrdersByOutTradeNo(out_trade_no);
	if (!payOrder) {
		return { code: -2, msg: '订单不存在或订单未支付!' }
	}
	const pay_type = payOrder.pay_type;
	let uniPayInstance = await vkPay.initUniPayInstance(payOrder);
	let queryResult;
	try {
		let refundQueryJson = {};
		if (pay_type.indexOf("alipay_") == 0) {
			// 支付宝必填 outRefundNo
			refundQueryJson = { outTradeNo: out_trade_no, outRefundNo: payOrder.refund_list[0].out_refund_no };
		} else {
			refundQueryJson = { outTradeNo: out_trade_no };
		}
		queryResult = await uniPayInstance.refundQuery(refundQueryJson);
	} catch (err) {
		return {
			code: -1,
			msg: "查询失败,请稍后再试!",
			err: err,
			queryResult: queryResult
		}
	}
	let orderInfo = {
		total_fee: payOrder.total_fee,
		refund_fee: payOrder.refund_fee,
		refund_num: payOrder.refund_num,
		refund_list: payOrder.refund_list,
		pay_type: payOrder.pay_type,
		status: payOrder.status,
		type: payOrder.type,
		out_trade_no: payOrder.out_trade_no,
		transaction_id: payOrder.transaction_id,
	};
	if (queryResult.refundFee > 0) {
		let msg = "退款成功";
		if (payOrder.refund_list && payOrder.refund_list.length > 0) {
			msg = `合计退款 ${payOrder.refund_fee/100}\r\n`;
			for (let i in payOrder.refund_list) {
				let item = payOrder.refund_list[i];
				let index = Number(i) + 1;
				let timeStr = fnUtil.timeFormat(item.refund_time, "yyyy-MM-dd hh:mm:ss");
				msg += `${index}、 ${timeStr} \r\n退款 ${item.refund_fee/100} \r\n`;
			}
		}
		res = {
			code: 0,
			msg: msg,
			orderInfo: orderInfo,
			queryResult: queryResult
		}
	} else {
		res = {
			code: -1,
			msg: "未退款",
			orderInfo: orderInfo,
			queryResult: queryResult
		}
	}

	return res;
};
/**
 * 统一退款
 * @description 当交易发生之后一段时间内，由于买家或者卖家的原因需要退款时，卖家可以通过退款接口将支付款退还给买家。
 * data 请求参数 说明
 * @param {String} out_trade_no 商户订单号
 * @param {String} out_refund_no 商户退款单号（不填会自动生成）
 * @param {String} refund_desc 退款原因
 * @param {Number} refund_fee 退款总金额
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.refund = async (obj = {}) => {
	let {
		out_trade_no,
		out_refund_no,
		refund_desc = "用户申请退款",
		refund_fee: myRefundFee
	} = obj;

	let res = { code: 0, msg: 'ok' };
	// 业务逻辑开始-----------------------------------------------------------
	if (!out_trade_no) {
		return { code: -1, msg: '订单号不能为空' }
	}
	let payOrder = await payDao.findPayOrdersByOutTradeNo(out_trade_no);
	if (!payOrder) {
		return { code: -2, msg: '订单不存在或订单未支付!' }
	}
	let refund_num = payOrder.refund_num || 0;
	refund_num++;
	// 生成退款订单号
	let outRefundNo = `${out_trade_no}-${refund_num}`;
	if (out_refund_no) {
		outRefundNo = out_refund_no;
	}
	// 订单总金额
	let totalFee = payOrder.total_fee;
	// 退款总金额
	let refundFee = myRefundFee || totalFee;
	const pay_type = payOrder.pay_type;
	let uniPayInstance = await vkPay.initUniPayInstance(payOrder);

	console.log(`---- ${out_trade_no} -- ${outRefundNo} -- ${totalFee/100} -- ${refundFee/100}`)
	// 退款操作
	try {
		res = await uniPayInstance.refund({
			outTradeNo: out_trade_no,
			outRefundNo,
			totalFee,
			refundFee,
			refundDesc: refund_desc
		});
	} catch (err) {
		console.error(err);
		return { code: -1, msg: err.message, err }
	}
	if (res.refundFee) {
		res.code = 0;
		res.msg = "退款成功";
		// 修改数据库
		try {
			let totalRefundFee = res.refundFee;
			if (pay_type.indexOf("wxpay_") == 0) {
				let queryResult = await uniPayInstance.refundQuery({ outTradeNo: out_trade_no });
				totalRefundFee = queryResult.refundFee;
			}
			let status = (totalRefundFee == totalFee) ? 3 : 2;
			// 记录每次的退款详情
			await payDao.updatePayOrdersById(payOrder._id, {
				status: status,
				refund_fee: totalRefundFee,
				refund_num: refund_num,
				refund_list: _.unshift({
					refund_time: new Date().getTime(),
					refund_fee: refundFee,
					out_refund_no: outRefundNo,
					refund_desc
				})
			});
		} catch (err) {
			console.error(err);
		}
	} else {
		res.code = -1;
		res.msg = "退款失败";
		if (res.subMsg) res.msg = res.subMsg;
	}
	// 业务逻辑结束-----------------------------------------------------------
	return res;
};

/**
 * 转账到支付宝或微信零钱
 * @description 单笔转账接口是基于支付宝的资金处理能力
 * data 请求参数 说明
 * @param {String} account 收款人账号（支付宝专用）
 * @param {String} real_name 收款人姓名
 * @param {String} openid 当前用户的openid（微信专用）
 * @param {Number} amount 必填项，转账金额 单位分 100=1元
 * @param {String} out_biz_no 转账单号（需保证唯一）
 * @param {String} title 转账标题
 * @param {String} remark 转账备注
 * @param {String} pay_type 必填项，支付类型：wxpay（微信支付）、alipay（支付宝支付）
 * @param {String} version 微信支付 支持 2和3
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.transfer = async (obj = {}) => {
	let { pay_type, version = 2 } = obj;
	if (pay_type === "alipay") {
		return vkPay.alipay.transfer(obj);
	} else if (pay_type === "wxpay") {
		if (version === 2) {
			return vkPay.wxpay.transfer(obj);
		} else {
			return vkPay.wxpay.transfer3(obj);
		}
	} else {
		return { code: -1, msg: "不支持的支付方式" };
	}
};


/**
 * 获取对应支付配置
 * @param {String} pay_type 支付方式
 * @param {String} app_auth_token 商户授权token
 * let uniPayConifg = await vkPay.getUniPayConfig({ pay_type, app_auth_token });
 */
vkPay.getUniPayConfig = async function(obj = {}) {
	let {
		pay_type,
		app_auth_token,
		pid, // 商户id，若此参数有值，则会从数据库中获取支付配置进行支付
	} = obj;
	if (pid || config.alipay || config.wxpay) {
		// 新版本
		let payConfig;
		if (pid) {
			// 从数据库中获取支付配置
			payConfig = await payDao.findPayConfigById(pid);
		} else {
			// 从配置文件中获取支付配置
			payConfig = config;
		}
		let arr = pay_type.split("_");
		let type1 = arr[0];
		let type2 = arr[1];
		if (payConfig && payConfig[type1] && payConfig[type1][type2]) {
			let uniPayConfig = payConfig[type1][type2];
			if (type1 === "alipay") {
				if (type2 === "service") {
					// 支付宝服务商模式需要额外增加 appAuthToken 参数
					uniPayConfig.appAuthToken = app_auth_token;
				}
			}
			if (type1 === "wxpay" && uniPayConfig.pfx) {
				// 微信支付证书 base64 转 buffer （数据库存的是 base64 格式的证书，而接口需要 buffer 格式的证书）
				uniPayConfig.pfx = Buffer.from(uniPayConfig.pfx, "base64");
			}
			return uniPayConfig;
		} else {
			throw new Error(`msg:${pay_type} : 商户支付配置错误`);
		}
	} else {
		// 老版本
		// 从配置文件中获取支付配置
		const {
			wxConfigMp,
			wxConfigApp,
			wxConfigH5,
			wxConfigMweb,
			wxConfigH5Weixin,
			aliConfigMp,
			aliConfigApp,
			aliConfigH5,
			aliConfigTransfer
		} = config;
		let uniPayConfig;
		switch (pay_type) {
			case "wxpay_mp-weixin":
				// 微信支付 - 小程序
				uniPayConfig = wxConfigMp;
				break;
			case "wxpay_app-plus":
				// 微信支付 - APP
				uniPayConfig = wxConfigApp;
				break;
			case "wxpay_h5":
				// 微信支付 - 网站二维码支付
				uniPayConfig = wxConfigH5;
				break;
			case "wxpay_mweb":
				// 微信支付 - 手机外部浏览器H5
				uniPayConfig = wxConfigMweb;
				break;
			case "wxpay_h5-weixin":
				// 微信支付 - 公众号
				uniPayConfig = wxConfigH5Weixin;
				break;
			case "alipay_mp-alipay":
				// 支付宝支付 - 小程序
				uniPayConfig = aliConfigMp;
				break;
			case "alipay_app-plus":
				// 支付宝支付 - APP
				uniPayConfig = aliConfigApp;
				break;
			case "alipay_h5":
				// 支付宝支付 - 网站二维码支付、手机外部浏览器H5
				uniPayConfig = aliConfigH5;
				break;
			case "alipay_transfer":
				// 支付宝支付 - 支付宝资金转出接口
				uniPayConfig = aliConfigTransfer;
				break;
			default:
				throw new Error(`msg:不支持【${pay_type}】支付方式`);
				console.error({
					code: -1,
					msg: `不支持【${pay_type}】支付方式`,
					value: pay_type
				});
		}
		return uniPayConfig;
	}
};
/**
 * 初始化uniPayInstance
 * @param {String} pay_type 支付方式
 * @param {String} app_auth_token 商户授权token
 * let uniPayInstance = await vkPay.initUniPayInstance({ pay_type, app_auth_token, pid });
 * let uniPayInstance = await vkPay.initUniPayInstance(orderInfo);
 */
vkPay.initUniPayInstance = async function(obj = {}) {
	let uniPayConifg = await vkPay.getUniPayConfig(obj);
	let uniPayInstance;
	if (obj.pay_type.indexOf("wxpay_") == 0) {
		// 微信
		uniPayInstance = uniPay.initWeixin(uniPayConifg);
	} else {
		// 支付宝
		uniPayInstance = uniPay.initAlipay(uniPayConifg);
	}
	return uniPayInstance;
};
// 获取交易方式
vkPay.getTradeType = function(pay_type) {
	/**
	 * 非小程序支付、App支付时必填
	 * 微信
	 * MWEB 外部浏览器H5支付
	 * JSAPI 小程序和公众号支付
	 * NATIVE 网站二维码支付
	 * APP APP支付
	 * MICROPAY 付款码支付，付款码支付有单独的支付接口，所以接口不需要上传，该字段在对账单中会出现
	 * 支付宝
	 * JSAPI 支付宝小程序支付
	 * APP APP支付
	 * NATIVE 网站二维码支付、外部浏览器H5支付、APP支付也能用
	 */
	let dataObj = {
		"wxpay_mp-weixin": "JSAPI",
		"wxpay_app-plus": "APP",
		"wxpay_h5": "NATIVE",
		"wxpay_mweb": "MWEB",
		"wxpay_h5-weixin": "JSAPI",
		"alipay_app-plus": "APP",
		"alipay_h5": "NATIVE"
	};
	return dataObj[pay_type];
};


////////////////////////////支付宝开始///////////////////////////////
vkPay.alipay = {};
/**
 * 转账到支付宝
 * data 请求参数
 * @param {String} account 收款人账号（支付宝专用）
 * @param {String} real_name 收款人姓名
 * @param {Number} amount 转账金额 单位分 100=1元
 * @param {String} out_biz_no 转账单号
 * @param {String} title 转账标题
 * @param {String} remark 转账备注
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.alipay.transfer = async (obj = {}) => {
	let {
		account,
		real_name,
		amount,
		out_biz_no,
		title = "转账",
		remark,
		check_name = true,
		app_auth_token,
		pid
	} = obj;
	let platform = "transfer";
	platform = fnUtil.getPlatform(platform);
	if (typeof amount !== "number" || amount <= 0) {
		return { code: -1, msg: "amount必须大于0，注意：100=1元（单位分）" };
	}
	if (!account) return { code: -1, msg: "account不能为空。" };
	if (check_name && !real_name) return { code: -1, msg: "real_name不能为空。" };
	let time = new Date();
	if (!out_biz_no) out_biz_no = "ZZ" + fnUtil.timeFormat(time, "yyyyMMddhhmmssS") + fnUtil.random(13);
	// 金额要/100，因为单位是分
	amount = parseFloat((amount / 100).toFixed(2));
	// 获取支付配置
	let uniPayConifg = await vkPay.getUniPayConfig({ pay_type: "alipay_" + platform, app_auth_token, pid });
	if (!uniPayConifg.appId || !uniPayConifg.privateKey || !uniPayConifg.appCertSn || !uniPayConifg.alipayRootCertSn) {
		return { code: -1, msg: '未正确配置支付宝转账参数' };
	}
	let res = { code: 0, msg: '转账成功' };
	// 业务逻辑开始-----------------------------------------------------------
	// 请求函数
	let method = "alipay.fund.trans.uni.transfer";
	// 请求参数
	let params = {
		timestamp: fnUtil.timeFormat(time, "yyyy-MM-dd hh:mm:ss"),
		biz_content: {
			out_biz_no: out_biz_no,
			trans_amount: amount,
			product_code: "TRANS_ACCOUNT_NO_PWD",
			biz_scene: "DIRECT_TRANSFER",
			order_title: title,
			payee_info: {
				identity_type: check_name ? "ALIPAY_LOGON_ID" : "ALIPAY_USER_ID",
				identity: account,
				name: real_name,
			},
			remark
		}
	};
	if (app_auth_token) {
		params.app_auth_token = app_auth_token;
	}
	// 计算签名
	const signData = fnUtil.alipay.sign(method, params, uniPayConifg);
	// 格式化url和请求参数
	const { url, execParams } = fnUtil.alipay.formatUrl(signData);
	// 发起http请求
	let result = await fnUtil.request({
		url: url,
		method: "POST",
		data: execParams
	});
	let response = result.alipay_fund_trans_uni_transfer_response;
	if (response.code !== "10000") {
		res.code = response.sub_code;
		res.msg = response.sub_msg;
	} else {
		// 增加数据库转账成功记录

	}
	res.result = result;
	// 业务逻辑结束-----------------------------------------------------------
	return res;
};
////////////////////////////支付宝结束///////////////////////////////
////////////////////////////微信支付开始///////////////////////////////

vkPay.wxpay = {};
/**
 * 转账到微信零钱
 * data 请求参数
 * @param {String} openid 当前用户的openid
 * @param {String} real_name 收款人姓名
 * @param {Number} amount 转账金额 单位分 100=1元
 * @param {String} out_biz_no 转账单号
 * @param {String} title 转账标题
 * @param {String} remark 转账备注
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.wxpay.transfer = async (obj = {}) => {
	let {
		openid,
		real_name,
		amount,
		out_biz_no,
		title = "转账",
		remark = "转账",
		platform,
		check_name = true,
		pid
	} = obj;
	if (typeof amount !== "number" || amount <= 0) {
		return { code: -1, msg: "amount必须大于0，注意：100=1元（单位分）" };
	}
	if (platform == undefined) {
		return { code: -1, msg: "platform不能为空" };
	}
	platform = fnUtil.getPlatform(platform);
	if (!openid) return { code: -1, msg: "openid不能为空。" };
	if (check_name && !real_name) return { code: -1, msg: "real_name不能为空。" };
	if (!out_biz_no) out_biz_no = "ZZ" + fnUtil.timeFormat(new Date(), "yyyyMMddhhmmssS") + fnUtil.random(13);
	// 获取支付配置
	let uniPayConifg = await vkPay.getUniPayConfig({ pay_type: "wxpay_" + platform, pid });
	let res = { code: 0, msg: '转账成功' };
	// 业务逻辑开始-----------------------------------------------------------
	// 请求接口地址
	let serviceUrl = "https://api.mch.weixin.qq.com";
	let method = "mmpaymkttransfers/promotion/transfers";
	let url = `${serviceUrl}/${method}`;
	// 请求参数
	let params = {
		nonce_str: fnUtil.random(32),
		partner_trade_no: out_biz_no,
		openid,
		check_name: check_name ? "FORCE_CHECK" : "NO_CHECK",
		re_user_name: real_name,
		amount,
		desc: remark
	};
	// 计算签名
	const xmlStr = fnUtil.wxpay.getXML(url, params, uniPayConifg);
	// 构造请求参数
	let requestOptions = {
		url: url,
		method: "POST",
		dataType: 'text',
		data: xmlStr
	};
	requestOptions.pfx = uniPayConifg.pfx;
	requestOptions.passphrase = uniPayConifg.mchId;
	// 发起http请求
	let resultXMLStr = await fnUtil.request(requestOptions);
	let result = fnUtil.wxpay.parseXML(resultXMLStr);
	if (result.result_code !== "SUCCESS") {
		res.code = result.err_code;
		res.msg = result.err_code_des;
	} else {
		// 增加数据库转账成功记录

	}
	delete result.mch_appid;
	delete result.mchid;
	res.result = result;
	// 业务逻辑结束-----------------------------------------------------------
	return res;
};

/**
 * 转账到微信零钱（v3版本）支持批量转账，批量转账请看vk-pay官方文档
 * data 请求参数
 * @param {String} openid 当前用户的openid
 * @param {String} real_name 收款人姓名
 * @param {Number} amount 转账金额 单位分 100=1元
 * @param {String} out_biz_no 转账单号
 * @param {String} title 转账标题
 * @param {String} remark 转账备注
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.wxpay.transfer3 = async (obj = {}) => {
	let res = { code: 0, msg: '' };
	let {
		openid,
		real_name,
		amount,
		out_biz_no,
		title = "转账",
		remark = "转账",
		pid,
		transfer_detail_list
	} = obj;
	let platform = "transfer";

	if (platform == undefined) {
		return { code: -1, msg: "platform不能为空" };
	}
	platform = fnUtil.getPlatform(platform);
	if (!out_biz_no) out_biz_no = "ZZ" + fnUtil.timeFormat(new Date(), "yyyyMMddhhmmssS") + fnUtil.random(13);
	// 获取支付配置
	let uniPayConifg = await vkPay.getUniPayConfig({ pay_type: "wxpay_" + platform, pid });

	let requestData;
	if (transfer_detail_list) {
		// 批量转账
		requestData = {
			out_batch_no: out_biz_no,
			batch_name: obj.batch_name,
			batch_remark: obj.batch_remark,
			total_amount: obj.total_amount,
			total_num: transfer_detail_list.length,
			transfer_detail_list,
		};
	} else {
		// 单笔转账
		if (typeof amount !== "number" || amount <= 0) {
			return { code: -1, msg: "amount必须大于0，注意：100=1元（单位分）" };
		}
		if (!openid) return { code: -1, msg: "openid不能为空。" };
		transfer_detail_list = [{
			out_detail_no: out_biz_no,
			transfer_amount: amount,
			transfer_remark: remark,
			openid,
			real_name,
		}];
		requestData = {
			out_batch_no: out_biz_no,
			batch_name: title,
			batch_remark: remark,
			total_amount: amount,
			total_num: transfer_detail_list.length,
			transfer_detail_list,
		};
	}

	// 实例化
	let wxPayV3 = new WxPayV3(uniPayConifg);
	let result = await wxPayV3.transfer(requestData);
	if (result.code) {
		return result;
	}
	res.msg = "转账申请已提交，请等待商家审核！";
	res.result = result;
	// 业务逻辑结束-----------------------------------------------------------
	return res;
};

/**
 * 获取微信支付平台证书v3版本
 */
vkPay.wxpay.fetchCertificates = async (obj = {}) => {
	let res = { code: 0, msg: 'ok' };
	let {
		pid,
		platform = "transfer"
	} = obj;

	if (platform == undefined) {
		return { code: -1, msg: "platform不能为空" };
	}
	platform = fnUtil.getPlatform(platform);
	// 获取支付配置
	let uniPayConifg = await vkPay.getUniPayConfig({ pay_type: "wxpay_" + platform, pid });

	// 实例化
	let wxPayV3 = new WxPayV3(uniPayConifg);
	let result = await wxPayV3.fetchCertificates();
	if (result.code) {
		return result;
	}
	try {
		res.wxpayPublicCertSn = result.data[0].serial_no;
		let ciphertext = result.data[0].encrypt_certificate.ciphertext;
		let associated_data = result.data[0].encrypt_certificate.associated_data;
		let nonce = result.data[0].encrypt_certificate.nonce;
		res.wxpayPublicCertContent = wxPayV3.decipherGcm(ciphertext, associated_data, nonce);
	} catch (err) {}

	res.result = result;
	// 业务逻辑结束-----------------------------------------------------------
	return res;
};

////////////////////////////微信支付结束///////////////////////////////
/**
 * code换取openid(微信公众号)
 * data 请求参数
 * @param {String} openid 当前用户的openid
 * @param {String} real_name 收款人姓名
 * @param {Number} amount 转账金额 单位分 100=1元
 * @param {String} out_biz_no 转账单号
 * @param {String} title 转账标题
 * @param {String} remark 转账备注
 * res 返回参数说明
 * @param {Number} code 错误码，0表示成功
 * @param {String} msg 详细信息
 */
vkPay.code2SessionWeixinH5 = async (data = {}) => {
	let { code } = data;
	if (!code) return { code: -1, msg: "code不能为空" };
	// 获取公众号支付的配置信息
	let uniPayConfig = await vkPay.getUniPayConfig({ pay_type: "wxpay_h5-weixin" });
	let requestRes = await fnUtil.request({
		url: `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${uniPayConfig.appId}&secret=${uniPayConfig.secret}&code=${code}&grant_type=authorization_code`
	});
	if (requestRes.errcode) {
		let msg = requestRes.errmsg;
		if (requestRes.errcode === 40163) msg = "该code已被使用，请重新获取";
		return {
			...requestRes,
			code: requestRes.errcode,
			msg
		};
	}
	// 转驼峰
	requestRes = fnUtil.snake2camelJson(requestRes);
	return {
		...requestRes,
		code: 0,
		msg: "ok"
	};
};

module.exports = vkPay;
